#ifndef _LEXER_BASIC_HPP
#define _LEXER_BASIC_HPP 1
#include<unordered_map>
#include<map>
#include<set>
#include<vector>
#include<string>
#include<functional>
#include<cmath>
#include<iostream>


template<typename idx,typename to>
using UMap=std::unordered_map<idx,to>;
using NumType=long double; // set the value type

enum Token{
    /* error, */number,oper,func,identifier
};
/**
 * oper
 * =
 * - +
 * * / %
 * ^
*/
enum Oper{
    assign,minus=10,plus,mult=20,divi=21,mod=22,fac=30// 阶乘 factorial
    ,dot,semicolons
    // ,fnc=42 function
    // ,undef=404
};
enum Func{ // (u|bi)nary operator
    Sin,Cos,Tan,Max,Min,Sqrt,
    unary,binary
};



bool operator<(Oper const lhs,Oper const rhs){
    return rhs-lhs>5; // at least 7
}

bool operator==(Oper const lhs,Oper const rhs){ // the same priority
    return std::abs(rhs-lhs)<3;
}

namespace LocalData{
    // --- type sector --- //
    using Str=std::string;

    // --- data sector --- //


    // --- class sector --- //
    enum Exception{
        NO_RESULT,DIV_BY_0,NO_FOUND,NODATA
    };
    enum NumberType{
        nt_short,nt_ushort,nt_float,nt_uf,
        nt_int,nt_uint,nt_double,nt_ud,
        nt_long,nt_ulong,nt_ld,nt_uld,nt_self // self defined
    };
    class SymbolTable{
        static UMap<std::string,NumType>Symbols;
            SymbolTable(){}
        public:
            ~SymbolTable(){}
            static SymbolTable& object(){
                static SymbolTable st;
            }
            static NumType Get(std::string const&id){
                return object().get(id);
            }
            static SymbolTable& Push(std::string const&id,const NumType val){
                return object().push(id,val);
            }
            NumType get(std::string const&id){
                try{
                    auto&& it=Symbols.at(id);
                    return it;
                }catch(const std::exception& e){
                    throw NO_FOUND;
                }
            }
            SymbolTable& push(std::string const&id,const NumType val){
                Symbols[id]=val;
                return *this;
            }
    };
    namespace cal_type{
        // type reflection through template
        template<typename T>
        struct type_cast{
            const static NumberType type=nt_self;
        };
        template<>struct type_cast<short>{
            const static NumberType type=nt_short;
        };
        template<>struct type_cast<float>{
            const static NumberType type=nt_float;
        };
        template<>struct type_cast<int>{
            const static NumberType type=nt_int;
        };
        template<>struct type_cast<double>{
            const static NumberType type=nt_double;
        };
        template<>struct type_cast<long>{
            const static NumberType type=nt_long;
        };
        template<>struct type_cast<long double>{
            const static NumberType type=nt_ld;
        };
        // template<>struct type_cast<unsigned short>{
        //     const static NumberType type=nt_ushort;
        // };
        // template<>struct type_cast<unsigned float>{
        //     const static NumberType type=nt_uf;
        // };
        // template<>struct type_cast<unsigned int>{
        //     const static NumberType type=nt_uint;
        // };
        // template<>struct type_cast<unsigned double>{
        //     const static NumberType type=nt_ud;
        // };
        // template<>struct type_cast<unsigned long>{
        //     const static NumberType type=nt_ulong;
        // };
        // template<>struct type_cast<unsigned long double>{
        //     const static NumberType type=nt_ulong double;
        // };
    }

    class DataSingleton final{
        private:
            DataSingleton(){
                OpLoad();FnLoad();TpLoad();UfLoad();BfLoad();
            }
            void OpLoad(){
                str2op["="]=assign; // =
                str2op["-"]=minus; // -
                str2op["+"]=plus; // +
                str2op["*"]=mult; // *
                str2op["/"]=divi; // /
                str2op["%"]=mod; // %
                str2op["^"]=fac; // ^
                str2op["."]=dot; // .
            }
            void FnLoad(){
                str2fn["sin"]=Sin; // sin
                str2fn["cos"]=Cos; // cos
                str2fn["tan"]=Tan; // tan
                str2fn["max"]=Max; // max
                str2fn["min"]=Min; // min
                str2fn["sqrt"]=Sqrt; // sqrt
            }
            void TpLoad(){
                Tk2str[number]="number";
                Tk2str[oper]="oper";
                Tk2str[func]="func";
                Tk2str[identifier]="identifier";
            }
            void UfLoad(){ // unary fn table load
                switch(cal_type::type_cast<NumType>::type){
                    case nt_float:
                        unaryFn[Sin]=sinf;
                        unaryFn[Cos]=cosf;
                        unaryFn[Tan]=tanf;
                        unaryFn[Sqrt]=sqrtf;
                        break;
                    case nt_double:
                        unaryFn[Sin]=sin;
                        unaryFn[Cos]=cos;
                        unaryFn[Tan]=tan;
                        unaryFn[Sqrt]=sqrt;
                        break;
                    case nt_ld:
                        unaryFn[Sin]=sinl;
                        unaryFn[Cos]=cosl;
                        unaryFn[Tan]=tanl;
                        unaryFn[Sqrt]=sqrtl;
                        break;
                }
                // unaryFn[Sin]=std::sin;
                // unaryFn[Cos]=std::cos;
                // unaryFn[Tan]=std::tan;
                // unaryFn[Sqrt]=std::sqrt;
            }
            void BfLoad(){ // binary fn table load
                binaryFn[Max]=std::max<NumType>;
                binaryFn[Min]=std::min<NumType>;
            }
        public:
            UMap<Str,Oper> str2op;
            UMap<Str,Func> str2fn;
            UMap<Token,Str> Tk2str;
            UMap<Func,std::function<NumType(NumType,NumType)>>binaryFn;
            UMap<Func,std::function<NumType(NumType)>>unaryFn;
            ~DataSingleton(){}
            static const DataSingleton& entity(){
                static DataSingleton obj;
                return obj;
            }
            static Func fnType(Func f){
                switch(f){
                    case Sin: case Cos: case Tan: case Sqrt:
                    case unary:
                        return unary;
                    case Max: case Min: case binary:
                        return binary;
                }
            }
    };
    // --- function sector --- //

    [[deprecated]]void dataLoad(){ // seem to be deprecated
        static bool done=false;
        if(!done){ // load data
            
            done=true;
        }
    }
    NumType str2Num(Str const&str){}
    NumType to_number(Str const&str){
        switch(cal_type::type_cast<NumType>::type){
            case nt_float:
                return std::stof(str);
            case nt_int:
                return std::stoi(str);
            case nt_double:
                return std::stod(str);
            case nt_long:
                return std::stol(str);
            case nt_ulong:
                return std::stoul(str);
            case nt_ld:
                return std::stold(str);
            case nt_self:
                return str2Num(str);
        }
    }
}

class token_t{
        Token tk; // constant value
    protected:
        std::string value;
    public:
        token_t(Token t):tk(t){}
        token_t(Token t,std::string const&v):tk(t),value(v){}
        virtual std::string getValue()const noexcept{return value;}
        Token getToken()const noexcept{return tk;}
        virtual ~token_t(){}
};
class number_t; // number type definition needed
class oper_t:public token_t{
    Oper op;
    public:
        oper_t(std::string const&opt):token_t(oper,opt){
            try{
                op=LocalData::DataSingleton::entity().str2op.at(opt);
            }catch(...){/* impossible */}
        }
        virtual ~oper_t(){}
        Oper getOper()const noexcept{return op;}
        
};
class number_t:public token_t{
    NumType num;
    public:
        number_t(std::string const&n):token_t(number,n),num(LocalData::to_number(n)){}
        virtual ~number_t(){}
        NumType getNum()const noexcept{return num;}
};
class func_t:public token_t{
    // std::function fn;
    public:
        // func_t(std::string const&fun):token_t(func,fun),fn(){}
        func_t(std::string const&fun):token_t(func,fun){}
        virtual ~func_t()=default;
};
class identifier_t:public token_t{
    public:
        identifier_t(std::string const&idt):token_t(identifier,idt){}
        virtual ~identifier_t()=default;
};
#endif//LEXER_BASIC_HPP
